---
title: Building a GraphQL API
subtitle: Learn how to build a GraphQL API using Encore
seotitle: How to build a GraphQL API using Encore.ts
seodesc: Learn how to build a microservices backend in TypeScript, powered by GraphQL and Encore.ts
lang: ts
---

Encore has great support for GraphQL with its type-safe approach to building APIs.

Encore's automatic tracing also makes it easy to find and fix
performance issues that often arise in GraphQL APIs (like the [N+1 problem](https://hygraph.com/blog/graphql-n-1-problem)).

In this tutorial we will build a GraphQL API using [Apollo](https://www.apollographql.com/docs/apollo-server/) and Encore.ts.

The final code will look like this:

<div className="not-prose my-10">
 <Editor projectName="graphqlTS" />
</div>

<Callout type="info">

To make it easier to follow along, we've laid out a trail of croissants to guide your way.
Whenever you see a 🥐 it means there's something for you to do.

</Callout>

## 1. Create your Encore application

🥐 Create a new application by running `encore app create` and select `Empty app` as the template.

If this is the first time you're using Encore, you'll be asked if you wish to create a free account. This is optional, but is needed when you want Encore to manage functionality like secrets and handle cloud deployments (which we'll use later on in the tutorial).

## 2. GraphQL setup

First, we need to install the necessary dependencies:

🥐 Update your `package.json` file to look like this:

```json
-- package.json --
{
  "name": "encore-graphql",
  "private": true,
  "version": "0.0.1",
  "license": "MPL-2.0",
  "type": "module",
  "scripts": {
    "generate": "graphql-codegen --config codegen.yml"
  },
  "devDependencies": {
    "@types/node": "^20.5.7",
    "typescript": "^5.2.2",
    "@graphql-codegen/cli": "2.16.5",
    "@graphql-codegen/typescript": "2.8.8",
    "@graphql-codegen/typescript-resolvers": "2.7.13"
  },
  "dependencies": {
    "@apollo/server": "^4.11.0",
    "encore.dev": "^1.35.3",
    "graphql": "^16.9.0",
    "graphql-tag": "^2.12.6"
  }
}
```

🥐 Run `npm install` to install the dependencies.

🥐 Next, create a `codegen.yml` file in the application root containing:

```
-- codegen.yml --
# This configuration file tells GraphQL Code Generator how to generate types based on our schema.
schema: './schema.graphql'
generates:
  # Specify where our generated types should live.
  ./graphql/__generated__/resolvers-types.ts:
    plugins:
      - 'typescript'
      - 'typescript-resolvers'
    config:
      useIndexSignature: true
```

## 3. Add GraphQL schema

Now it's time to define the GraphQL schema.

🥐 Create a `schema.graphql` file in the application root containing:

```
-- schema.graphql --
type Query {
  books: [Book]
}

type Book {
  title: String!
  author: String!
}

type AddBookMutationResponse {
  code: String!
  success: Boolean!
  message: String!
  book: Book
}

type Mutation {
  addBook(title: String!, author: String!): AddBookMutationResponse
}
```

🥐 Run the code generation script to generate the resolver types:

```shell
$ npm run generate
```
The types will be written to `graphql/__generated__/resolvers-types.ts` and will contain a bunch of types that we can use when implementing the resolvers.

## 4. Create a Book service

Let's create a simple book service that we can later query using GraphQL. It's a good idea to to make the GraphQL library query Encore endpoints because that will result in traces being created for each called endpoint. Having tracing makes it easy to find and fix performance issues that often arise in GraphQL APIs.

🥐 In your application's root folder, create a directory named `book` containing a file named `encore.service.ts`.

```shell
$ mkdir book
$ touch book/encore.service.ts
```

🥐 Add the following code to `book/encore.service.ts`:

```ts
-- book/encore.service.ts --
import { Service } from "encore.dev/service";

export default new Service("book");
```

This is how you define a service with Encore. Encore will now consider files in the `book` directory and all its subdirectories as part of the `book` service.

🥐 Next, create a `book/book.ts` file containing:

```ts
import { api, APIError } from "encore.dev/api";
import { Book } from "../graphql/__generated__/resolvers-types";

const db: Book[] = [
  {
    title: "To Kill a Mockingbird",
    author: "Harper Lee",
  },
  {
    title: "1984",
    author: "George Orwell",
  },
  {
    title: "The Great Gatsby",
    author: "F. Scott Fitzgerald",
  },
  {
    title: "Moby-Dick",
    author: "Herman Melville",
  },
  {
    title: "Pride and Prejudice",
    author: "Jane Austen",
  },
];

export const list = api(
  { expose: true, method: "GET", path: "/books" },
  async (): Promise<{ books: Book[] }> => {
    return { books: db };
  },
);

// Omit the "__typename" field from the request
type AddRequest = Omit<Required<Book>, "__typename">;

export const add = api(
  { expose: true, method: "POST", path: "/book" },
  async (book: AddRequest): Promise<{ book: Book }> => {
    if (db.some((b) => b.title === book.title)) {
      throw APIError.alreadyExists(
        `Book "${book.title}" is already in database`,
      );
    }
    db.push(book);
    return { book };
  },
);
```

The `book` service contains two endpoint, one for listing all books and another to add a new book to the database. Our "database" is hardcoded just to limit the scope of this example. Take a look at the [Using SQL databases](/docs/ts/primitives/databases) docs to learn how to set up and use a database.

We get the `Book` type from the generated resolver types. This will make it easier later when we create the resolver functions.

## 5. Create the GraphQL service

Now it's time to create our Encore service that will provide the GraphQL API.

🥐 In the `graphql` directory, add a `encore.service.ts` file with the following content:

```ts
-- graphql/encore.service.ts --
import { Service } from "encore.dev/service";

export default new Service("graphql");
```

  Now, we need to create resolvers that call the `book` service. Since the GraphQL API uses the same types as the Encore API exposes (we import types form `resolvers-types.ts` in `book.ts`), our resolver can just be thin wrapper around out API endpoints.

🥐 Create the directory `resolvers` in the `graphql` directory. In the resolvers directory we want to place three files: `index.ts`, `queries.ts` and `mutations.ts`:

```ts
-- resolvers/index.ts --
import { Resolvers } from "../__generated__/resolvers-types";
import Query from "./queries.js";
import Mutation from "./mutations.js";

const resolvers: Resolvers = { Query, Mutation };

export default resolvers;
-- resolvers/queries.ts --
import { book } from "~encore/clients";
import { QueryResolvers } from "../__generated__/resolvers-types";

// Use the generated `QueryResolvers` type to type check our queries!
const queries: QueryResolvers = {
  books: async () => {
    const { books } = await book.list();
    return books;
  },
};

export default queries;
-- resolvers/mutations.ts --
import { book } from "~encore/clients";
import { MutationResolvers } from "../__generated__/resolvers-types";
import { APIError } from "encore.dev/api";

// Use the generated `MutationResolvers` type to type check our mutations
const mutations: MutationResolvers = {
  addBook: async (_, { title, author }) => {
    try {
      const resp = await book.add({ title, author });
      return {
        book: resp.book,
        success: true,
        code: "ok",
        message: "New book added",
      };
    } catch (err) {
      const apiError = err as APIError;

      return {
        book: null,
        success: false,
        code: apiError.code,
        message: apiError.message,
      };
    }
  },
};

export default mutations;
```

Now we are ready can create the ApolloServer that makes use of our resolvers and to expose our GraphQL endpoint.

🥐 Still in the `graphql` directory, create a `graphql.ts` file containing:

```ts
-- graphql/graphql.ts --
import { api } from "encore.dev/api";
import { ApolloServer, HeaderMap } from "@apollo/server";
import { readFileSync } from "node:fs";
import resolvers from "./resolvers";
import { json } from "node:stream/consumers";

const typeDefs = readFileSync("./schema.graphql", { encoding: "utf-8" });

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

await server.start();

export const graphqlAPI = api.raw(
  { expose: true, path: "/graphql", method: "*" },
  async (req, res) => {
    server.assertStarted("/graphql");

    const headers = new HeaderMap();
    for (const [key, value] of Object.entries(req.headers)) {
      if (value !== undefined) {
        headers.set(key, Array.isArray(value) ? value.join(", ") : value);
      }
    }

    // More on how to use executeHTTPGraphQLRequest: https://www.apollographql.com/docs/apollo-server/integrations/building-integrations/
    const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({
      httpGraphQLRequest: {
        headers,
        method: req.method!.toUpperCase(),
        body: await json(req),
        search: new URLSearchParams(req.url ?? "").toString(),
      },
      context: async () => {
        return { req, res };
      },
    });

    for (const [key, value] of httpGraphQLResponse.headers) {
      res.setHeader(key, value);
    }
    res.statusCode = httpGraphQLResponse.status || 200;

    if (httpGraphQLResponse.body.kind === "complete") {
      res.end(httpGraphQLResponse.body.string);
      return;
    }

    for await (const chunk of httpGraphQLResponse.body.asyncIterator) {
      res.write(chunk);
    }
    res.end();
  },
);
```

This creates an [Raw API endpoint](https://encore.dev/docs/ts/primitives/raw-endpoints) available on `/graphql`. In the endpoint we use ApolloServer to handle the GraphQL queries and mutations. We then return the response to the client.

If we were to use another GraphQL library other than Apollo, the concept would still be the same:
1. Take client requests with a Raw endpoint.
2. Pass along the request and response objects to the GraphQL library of your choice.
3. Use the library to handle the GraphQL queries and mutations.
4. Return the GraphQL response from the Raw endpoint.

## 6. Trying it out

With that, the GraphQL API is done!

🥐Try it out by running `encore run` and opening [https://studio.apollographql.com/sandbox](https://studio.apollographql.com/sandbox) in your browser. Set http://localhost:4000/graphql as your endpoint URL. You should now be able to read the schema and execute queries.

Enter the query:
```graphql
mutation AddBook {
  addBook(author: "J.R.R. Tolkien", title: "The Hobbit") {
    success
    message
    code
  }
}
```

Now try the GetBooks query:

```graphql
query GetBooks {
  books {
    author
    title
  }
}
```

And you should now see the "The Hobbit" in the list of books.

🥐 Try opening the Local Development Dashboard at [http://localhost:9400](http://localhost:9400) and view the traces that were generated when calling your GraphQL API.


## 7. Deploy

<Accordion>

### Self-hosting

Encore supports building Docker images directly from the CLI, which can then be self-hosted on your own infrastructure of choice.

If your app is using infrastructure resources, such as SQL databases, Pub/Sub, or metrics, you will need to supply a [runtime configuration](/docs/ts/self-host/configure-infra) your Docker image.

🥐 Build a Docker image by running `encore build docker graphql:v1.0`.

This will compile your application using the host machine and then produce a Docker image containing the compiled application.

🥐 Upload the Docker image to the cloud provider of your choice and run it.

</Accordion>

<Accordion>

### Encore Cloud (free)

Encore Cloud provides automated infrastructure and DevOps. Deploy to a free development environment or to your own cloud account on AWS or GCP.

### Create account

Before deploying with Encore Cloud, you need to have a free Encore Cloud account and link your app to the platform. If you already have an account, you can move on to the next step.

If you don’t have an account, the simplest way to get set up is by running `encore app create` and selecting **Y** when prompted to create a new account. Once your account is set up, continue creating a new app, selecting the `empty app` template.

After creating the app, copy your project files into the new app directory, ensuring that you do not replace the `encore.app` file (this file holds a unique id which links your app to the platform).

### Commit changes

The final step before you deploy is to commit all changes to the project repo.

🥐 Push your changes and deploy your application to Encore's free development cloud by running:

```shell
$ git add -A .
$ git commit -m 'Initial commit'
$ git push encore
```

Encore will now build and test your app, provision the needed infrastructure, and deploy your application to the cloud.

After triggering the deployment, you will see a URL where you can view its progress in the [Encore Cloud dashboard](https://app.encore.cloud). It will look something like: `https://app.encore.cloud/$APP_ID/deploys/...`

From there you can also see metrics, traces, link your app to a GitHub repo to get automatic deploys on new commits, and connect your own AWS or GCP account to use for production deployment.

### Celebrate with fireworks

Now that your app is running in the cloud, let's celebrate with some fireworks:

🥐 In the Cloud Dashboard, open the Command Menu by pressing **Cmd + K** (Mac) or **Ctrl + K** (Windows/Linux).

_From here you can easily access all Cloud Dashboard features and for example jump straight to specific services in the Service Catalog or view Traces for specific endpoints._

🥐 Type `fireworks` in the Command Menu and press enter. Sit back and enjoy the show!

![Fireworks](/assets/docs/fireworks.jpg)

</Accordion>

## Conclusion

We've now built a GraphQL API gateway that forwards requests to the application's underlying Encore services in a type-safe way with minimal boilerplate.

Note that the concepts discussed here are general and can be easily adapted to any GraphQL schema.

Whenever you make a change to the schema or configuration, re-run `npm run generate` to
regenerate the resolver types.
